#include <iostream>
#include <exception>
#include <cstdlib>

#include <gflags/gflags.h>

#include "OpenCVImage.h"
#include "CompositeImage.h"
#include "PanoramaDownloader.h"
#include "AppliedWarp.h"
#include "Warper.h"
#include "NearestInterpolator.h"
#include "BilinearInterpolator.h"

using namespace std;

// Dimensions of output image.
DEFINE_int32(width, 512, "Output width");
DEFINE_int32(height, 512, "Output height");

// Server to hit for Google Street View tiles.
DEFINE_string(server, "http://cbk0.google.com/cbk", "Panorama server");

// Destination to save the output file.
DEFINE_string(outfile, "", "Output file");

// Street View panorama ID.
DEFINE_string(pano, "", "Google panorama ID");

// Panorama zoom.
DEFINE_int32(pano_zoom, 1, "Panorama zoom");
// Dimensions of panorama at zoom level zero.
DEFINE_int32(pano_width, 416, "Panorama width");
DEFINE_int32(pano_height, 208, "Panorama height");

// Output dimensions.
DEFINE_int32(tile_width, 512, "Tile width");
DEFINE_int32(tile_height, 512, "Tile height");

// HTTP request timeout in milliseconds.
DEFINE_int32(timeout, 1000, "HTTP timeout");
// User agent in HTTP request.
DEFINE_string(user_agent,
              "Mozilla/5.0 (X11; U; Linux i686; en-US) "
              "AppleWebKit/533.4 (KHTML, like Gecko) "
              "Chrome/5.0.375.99 Safari/533.4",
              "HTTP user agent");

// Returns true if the flag is not empty.
bool validateNonEmpty(const char *flagname, const string& value) {
  return (value.length() != 0);
}

// Returns true if the flag is greater than zero.
bool validatePositive(const char *flagname, int value) {
  return (value > 0);
}

// Parses the command-line flags.
void parseFlags(int argc, char **argv) {
  // Set useage message.
  const string USAGE("Creates a planet from Google Street View panorama");
  google::SetUsageMessage(USAGE);

  // Ensure all widths and heights are positive.
  google::RegisterFlagValidator(&FLAGS_width, validatePositive);
  google::RegisterFlagValidator(&FLAGS_height, validatePositive);
  google::RegisterFlagValidator(&FLAGS_pano_width, validatePositive);
  google::RegisterFlagValidator(&FLAGS_pano_height, validatePositive);
  google::RegisterFlagValidator(&FLAGS_tile_width, validatePositive);
  google::RegisterFlagValidator(&FLAGS_tile_height, validatePositive);

  // Ensure panorama and output file are set.
  google::RegisterFlagValidator(&FLAGS_pano, validateNonEmpty);
  google::RegisterFlagValidator(&FLAGS_outfile, validateNonEmpty);

  google::ParseCommandLineFlags(&argc, &argv, true);
}

int main(int argc, char **argv) {
  parseFlags(argc, argv);

  // Street View image geometry.
  int pano_width = FLAGS_pano_width << FLAGS_pano_zoom;
  int pano_height = FLAGS_pano_height << FLAGS_pano_zoom;

  // Download the panorama.
  PanoramaDownloader downloader(FLAGS_pano, FLAGS_server, FLAGS_timeout,
      FLAGS_user_agent, pano_width, pano_height, FLAGS_tile_width,
      FLAGS_tile_height, FLAGS_pano_zoom);
  CompositeImage<OpenCVImage> panorama;
  downloader.download(&panorama);

  cerr << "Finished downloading" << endl;

  // Set up the planet warp.
  Warp warp;
  warp.setScaleToSize(FLAGS_width, FLAGS_height);
  warp.setCrop();
  AppliedWarp applied_warp(warp, panorama);

  // Use bilinear interpolation. Repeat horizontally but not vertically.
  BilinearInterpolator interpolator(TiledInterpolator::LOOP,
      TiledInterpolator::HOLD);

  // Do the warp.
  OpenCVImage planet;
  Warper warper(applied_warp, interpolator, &planet);
  warper.warp();

  planet.save(FLAGS_outfile);

  return 0;
}

